package buildings;

import buildings.office.Office;
import buildings.dwelling.Dwelling;
import buildings.dwelling.DwellingFactory;
import buildings.dwelling.DwellingFloor;
import buildings.exceptions.FloorIndexOutOfBoundsException;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Buildings {
    
    private static BuildingFactory buildingFactory = new DwellingFactory();
    
    public static void setBuildingFactory(BuildingFactory buildingFactory) {
        Buildings.buildingFactory = buildingFactory;
    }
    
    public static Building createBuilding(Floor[] floors) {
        return buildingFactory.createBuilding(floors);
    }
    
    public static Building createBuilding(int floorsCount, int[] spacesCounts) {
        return buildingFactory.createBuilding(floorsCount, spacesCounts);
    }
    
    public static Floor createFloor(Space[] spaces) {
        return buildingFactory.createFloor(spaces);
    }
    
    public static Floor createFloor(int spacesCount) {
        return buildingFactory.createFloor(spacesCount);
    }
    
    public static Space createSpace(int roomsCount, float area) {
        return buildingFactory.createSpace(roomsCount, area);
    }
    
    public static Space createSpace(float area) {
        return buildingFactory.createSpace(area);
    }
    
    public static void outputBuilding(Building building, OutputStream out)
            throws IOException {
        DataOutputStream stream = new DataOutputStream(out);
        stream.writeInt(building.getFloorCount());
        for (int i = 0; i < building.getFloorCount(); i++) {
            stream.writeInt(building.getFloorByNumber(i).getSpaceCount());
            for (int j = 0; j < building.getFloorByNumber(i).getSpaceCount(); j++) {
                stream.writeInt(building.getFloorByNumber(i).
                        getSpaceByNumber(j).getRoomCount());
                stream.writeFloat(building.getFloorByNumber(i).
                        getSpaceByNumber(j).getSquare());
            }
        }
        stream.flush();
    }
    
    public static Building inputBuilding(InputStream in)
            throws IOException {
        DataInputStream stream = new DataInputStream(in);
        int floorsCount = stream.readInt();
        int spacesCount;
        Floor[] floors = new Floor[floorsCount];
        for (int i = 0; i < floorsCount; i++) {
            spacesCount = stream.readInt();
            Floor floor = buildingFactory.createFloor(0);
            for (int j = 0; j < spacesCount; j++) {
                floor.addSpaceByNumber(j, buildingFactory.createSpace(stream.readInt(),
                        stream.readFloat()));
            }
            floors[i] = floor;
        }
        return buildingFactory.createBuilding(floors);
    }
    
    public static void writeBuilding(Building building, Writer out) {
        PrintWriter writer = new PrintWriter(out);
        writer.print(building.getFloorCount());
        writer.print(" ");
        for (int i = 0; i < building.getFloorCount(); i++) {
            writer.print(building.getFloorByNumber(i).getSpaceCount());
            writer.print(" ");
            for (int j = 0; j < building.getFloorByNumber(i).getSpaceCount(); j++) {
                writer.print(building.getFloorByNumber(i).
                        getSpaceByNumber(j).getRoomCount());
                writer.print(" ");
                writer.print(building.getFloorByNumber(i).
                        getSpaceByNumber(j).getSquare());
                writer.print(" ");
            }
        }
        writer.flush();
    }
    
    public static Building readBuilding(Reader in)
            throws IOException {
        StreamTokenizer stream = new StreamTokenizer(in);
        stream.parseNumbers();
        stream.nextToken();
        double floorsCount = stream.nval;
        double spacesCount;
        Floor[] floors = new Floor[(int) floorsCount];
        for (int i = 0; i < floorsCount; i++) {
            stream.nextToken();
            spacesCount = stream.nval;
            Floor floor = buildingFactory.createFloor(0);
            for (int j = 0; j < spacesCount; j++) {
                stream.nextToken();
                double roomCount = stream.nval;
                stream.nextToken();
                double area = stream.nval;
                floor.addSpaceByNumber(j, buildingFactory.createSpace((int) roomCount,
                        (float) area));
            }
            floors[i] = floor;
        }
        try {
            return buildingFactory.createBuilding(floors);
        } catch (FloorIndexOutOfBoundsException e) {
            return null;
        }
    }
    
    public static void SerializationWrite(Building building, String fName)
            throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(fName));
        out.writeObject(building);
        out.close();
    }
    
    public static Building SerializationRead(String fName)
            throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(fName));
        Building building = (Building) in.readObject();
        in.close();
        return building;
    }
    
    public static SynchronizedFloor synchronizedFloor(Floor floor) {
        return new SynchronizedFloor(floor);
    }
    
    public static Building createBuilding(Floor[] floors, Class buildingClass) {
        try {
            Constructor c = buildingClass.getConstructor(new Class[]{floors.getClass()});
            return (Building) c.newInstance(new Object[]{floors});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
    }
    
    public static Building createBuilding(int floorsCount, int[] spacesCounts,
            Class buildingClass) {
        try {
            Constructor c = buildingClass.getConstructor(new Class[]{Integer.TYPE,
                        spacesCounts.getClass()});
            return (Building) c.newInstance(new Object[]{floorsCount, spacesCounts});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
    }
    
    public static Floor createFloor(Space[] spaces, Class floorClass) {
        try {
            Constructor c = floorClass.getConstructor(new Class[]{spaces.getClass()});
            return (Floor) c.newInstance(new Object[]{spaces});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
    }
    
    public static Floor createFloor(int spacesCount, Class floorClass) {
        try {
            Constructor c = floorClass.getConstructor(new Class[]{Integer.TYPE});
            return (Floor) c.newInstance(new Object[]{spacesCount});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
    }
    
    public static Space createSpace(int roomsCount, float area,
            Class spaceClass) {
        try {
            Constructor c = spaceClass.getConstructor(new Class[]{Integer.TYPE, Float.TYPE});
            return (Space) c.newInstance(new Object[]{roomsCount, area});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
    }
    
    public static Space createSpace(float area, Class spaceClass) {
        try {
            Constructor c = spaceClass.getConstructor(new Class[]{Float.TYPE});
            return (Space) c.newInstance(new Object[]{area});
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new IllegalArgumentException();
        }
        
    }
    
    public static Building inputBuilding(InputStream in, Class buildingClass, 
            Class floorClass, Class spaceClass)
            throws IOException {
        DataInputStream stream = new DataInputStream(in);
        int floorsCount = stream.readInt();
        int spacesCount;      
        Floor[] floors = new Floor[floorsCount];
        for (int i = 0; i < floorsCount; i++) {
            spacesCount = stream.readInt();
            Floor floor = createFloor(0, floorClass);
            for (int j = 0; j < spacesCount; j++) {
                floor.addSpaceByNumber(j, createSpace(stream.readInt(), stream.readFloat(), spaceClass));
            }
            floors[i] = floor;
        }
        return createBuilding(floors, buildingClass);
    }
    
    public static Building readBuilding(Reader in, Class buildingClass, 
            Class floorClass, Class spaceClass)
            throws IOException {
        StreamTokenizer stream = new StreamTokenizer(in);
        stream.parseNumbers();
        stream.nextToken();
        double floorsCount = stream.nval;
        double spacesCount;
        Floor[] floors = new Floor[(int) floorsCount];
        for (int i = 0; i < floorsCount; i++) {
            stream.nextToken();
            spacesCount = stream.nval;
            Floor floor = createFloor(0, floorClass);
            for (int j = 0; j < spacesCount; j++) {
                stream.nextToken();
                double roomCount = stream.nval;
                stream.nextToken();
                double area = stream.nval;
                floor.addSpaceByNumber(0, createSpace((int) roomCount, 
                        (float) area, spaceClass));
            }
            floors[i] = floor;
        }
        try {
            return createBuilding(floors, buildingClass);
        } catch (FloorIndexOutOfBoundsException e) {
            return null;
        }
    }
}
